/*!
 * @file DFRobot_BMV080.h
 * @brief Declaration the basic structure of class DFRobot_BMV080
 * @n Support IIC and SPI communication interfaces
 * @n Used the official SDK of Bosch
 * @n Can obtain PM1, PM2.5, PM10
 * @copyright	Copyright (c) 2025 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @license The MIT License (MIT)
 * @author [lbx](liubx8023@gmail.com)
 * @version V1.0
 * @date 2025-09-15
 * @url https://github.com/DFRobot/DFRobot_BMV080
 */

#ifndef __DFROBOT_BMV080_H
#define __DFROBOT_BMV080_H

#include <Arduino.h>
#include "bmv080.h"
#include "bmv080_defs.h"
#include <Wire.h>
#include <SPI.h>

//Open this macro to see the detailed running process of the program
//#define ENABLE_DBG
#ifdef ENABLE_DBG
#define DBG(...) {Serial.print("[");Serial.print(__FUNCTION__); Serial.print("(): "); Serial.print(__LINE__); Serial.print(" ] "); Serial.println(__VA_ARGS__);}
#else
#define DBG(...)
#endif

// I2C address
#define DFRobot_BMV080_I2C_ADDR 0x57

// Operation mode
#define CONTINUOUS_MODE     0 ///< Continuous mode, sensor takes measurements continuously
#define DUTY_CYCLE_MODE     1 ///< Duty cycle mode, sensor takes measurements at specified intervals

// Measurement algorithm
#define FAST_RESPONSE       1 ///< response,suitable for scenarios requiring quick response
#define BALANCED            2 ///< Balanced, suitable for scenarios where a balance needs to be struck between precision and rapid response
#define HIGH_PRECISION      3 ///< High precision, suitable for scenarios requiring high accuracy


class DFRobot_BMV080 {
public:
  #define ERR_OK            0      ///< no error
  #define ERR_DATA_BUS      1      ///< data bus error
  #define ERR_DATA_READ     2      ///< data read error
  #define ERR_IC_VERSION    3      ///< IC version mismatch

public: 
  /**
   * @fn openBmv080
   * @brief Initialize the BMV080 sensor
   * @pre Must be called first in order to create the _handle_ required by other functions.
   * @post The _handle_ must be destroyed via _bmv080_close_.
   * @note This function usually only needs to be called once.
   * @note It must be called before any other functions that interact with the sensor.
   * @return 0 successful.
   * @return other values. See the bmv080_status_code_t enumeration in bmv080_defs.h for details.
   */
  uint16_t openBmv080(void);

  /**
   * @fn closeBmv080
   * @brief Turn off the sensor. The sensor will stop functioning. If you need to use it again, you need to call the openBmv080 function.
   * @pre Must be called last in order to destroy the _handle_ created by _bmv080_open_.
   * @return 1 successful.
   * @return 0 error, when the _handle_ is NULL or not called stopBmv080 function before.
   */
  bool closeBmv080(void);

  /**
   * @fn resetBmv080
   * @brief Reset a sensor unit including both hardware and software.
   * @pre A valid _handle_ generated by _bmv080_open_ is required. 
   * @post Any parameter changed through _bmv080_set_parameter_ is reverted back to its default.
   * @return 1 successful.
   * @return 0 error, when the _handle_ is NULL(may be not call openBmv080 or stopBmv080 function before).
   */
  bool resetBmv080(void);

  /**
   * @fn getBmv080DV
   * @brief Get the BMV080 sensor's driver version
   * @param major: Major version number
   * @param minor: Minor version number
   * @param patch: Patch version number
   * @return 1 successful.
   * @return 0 error.
   */
  bool getBmv080DV(uint16_t &major, uint16_t &minor, uint16_t &patch);

  /**
   * @fn getBmv080ID
   * @brief Get the BMV080 sensor's ID
   * @param id: Pointer to a char array to store the ID (must be at least 13 bytes long to accommodate the null terminator)
   * @return 1 successful.
   * @return 0 error.
   */
  bool getBmv080ID(char *id);

  /**
   * @fn getBmv080Data
   * @brief Get the BMV080 sensor's data
   * @param PM1: PM1.0 concentration (ug/m3)
   * @param PM2_5: PM2.5 concentration (ug/m3)
   * @param PM10: PM10 concentration (ug/m3)
   * @param allData: All data from the BMV080 sensor (optional),This is a structure. It has the following members.
   *                 runtime_in_sec: estimate of the time passed since the start of the measurement, in seconds
   *                 pm2_5_mass_concentration: PM2.5 value in ug/m3
   *                 pm1_mass_concentration: PM1 value in ug/m3
   *                 pm10_mass_concentration: PM10 value in ug/m3
   *                 pm2_5_number_concentration: PM2.5 value in particles/cm3
   *                 pm1_number_concentration: PM1 value in particles/cm3
   *                 pm10_number_concentration: PM10 value in particles/cm3
   *                 is_obstructed: flag to indicate whether the sensor is obstructed and cannot perform a valid measurement
   *                 is_outside_measurement_range: flag to indicate whether the PM2.5 concentration is outside the specified measurement range (0..1000 ug/m3)
   * @note This function should be called at least once every 1 second.When the BMV080 sensor data is ready, the function will return 1.
   * @return 1 successful, when the BMV080 sensor data is ready.
   * @return 0 unsuccessful, when the BMV080 sensor data is not ready.
   */
  bool getBmv080Data(float *PM1, float *PM2_5, float *PM10, bmv080_output_t *allData=NULL);

  /**
   * @fn setBmv080Mode
   * @brief Set the BMV080 sensor's mode.After calling this function, the sensor will start to collect data.
   * @param mode: The mode to set, either CONTINUOUS_MODE or DUTY_CYCLE_MODE
   *              CONTINUOUS_MODE: Sensor takes measurements continuously
   *              DUTY_CYCLE_MODE: Sensor takes measurements at specified intervals
   * @return 0 successful
   * @return -1 mode is invalid
   * @return -2 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
   * @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
   */
  int setBmv080Mode(uint8_t mode);

  /**
   * @fn stopBmv080
   * @brief Stop the measurement. If you need to continue the measurement, you need to call the setBmv080Mode function.
   * @pre Must be called at the end of a data acquisition cycle to ensure that the sensor unit is ready for the next measurement cycle.
   * @return 1 successful
   * @return 0 error 
   */
  bool stopBmv080(void);

  /**
   * @fn setIntegrationTime
   * @brief Set the measurement window.
   * @note In duty cycling mode, this measurement window is also the sensor ON time.
   * @param integration_time The measurement integration time in seconds (s).
   * @return 0 successful
   * @return -1 integration_time is invalid, must be greater than or equal to 1.0s
   * @return -2 duty_cycling_period must larger than integration_time by at least 2 seconds.
   * @return -3 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
   * @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
   */
  int setIntegrationTime(float integration_time);

  /**
   * @fn getIntegrationTime
   * @brief Get the current integration time.
   * @return The current integration time in seconds (s).
   * @return NAN error, or not call openBmv080 or stopBmv080 function before.
   */
  float getIntegrationTime(void);

  /**
   * @fn setDutyCyclingPeriod
   * @brief Set the duty cycling period.
   * @n Duty cycling period (sum of integration time and sensor OFF / sleep time).
   * @note This must be greater than integration time by at least 2 seconds.
   * @param duty_cycling_period The duty cycling period in seconds (s).
   * @return 0 successful
   * @return -1 duty_cycling_period is invalid, must be greater than or equal to 12s
   * @return -2 integration_time must less than duty_cycling_period by at least 2 seconds.
   * @return -3 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
   * @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
   */
  int setDutyCyclingPeriod(uint16_t duty_cycling_period);

  /**
   * @fn getDutyCyclingPeriod
   * @brief Get the current duty cycling period.
   * @return The current duty cycling period in seconds (s).
   * @return 0 error
   */
  uint16_t getDutyCyclingPeriod(void);

  /**
   * @fn setObstructionDetection
   * @brief Set if obstruction detection feature is enabled.
   * @param obstructed true to enable obstruction detection, false to disable.
   * @return 1 successful
   * @return 0 error, or not call openBmv080 or stopBmv080 function before.
   */
  bool setObstructionDetection(bool obstructed);

  /**
   * @fn getObstructionDetection
   * @brief Get if obstruction detection feature is enabled.
   * @return 1 if obstruction detection is enabled.
   * @return 0 if obstruction detection is disabled.
   * @return -1 error, or not call openBmv080 or stopBmv080 function before.
   */
  int getObstructionDetection(void);

  /**
   * @fn ifObstructed
   * @brief Check whether the sensor receiver is blocked.
   * @return 1 Obstructed
   * @return 0 not obstructed
   */
  bool ifObstructed(void);

    /**
   * @fn setDoVibrationFiltering
   * @brief Enable or disable the Do Vibration Filtering feature.
   * @param do_vibration_filtering 1 to enable, 0 to disable.
   * @return 1 successful
   * @return 0 error, or not call openBmv080 or stopBmv080 function before.
   */
  bool setDoVibrationFiltering(bool do_vibration_filtering);

  /**
   * @fn getDoVibrationFiltering
   * @brief Get the status of the Do Vibration Filtering feature.
   * @return 1 if vibration filtering is enabled.
   * @return 0 if vibration filtering is disabled.
   * @return -1 error, or not call openBmv080 or stopBmv080 function before.
   */
  int getDoVibrationFiltering(void);

  /**
   * @fn setMeasurementAlgorithm
   * @brief Set the measurement algorithm.
   * @param measurement_algorithm The measurement algorithm to use.
   *                              FAST_RESPONSE //Fast response,suitable for scenarios requiring quick response
   *                              BALANCED //Balanced, suitable for scenarios where a balance needs to be struck between precision and rapid response
   *                              HIGH_PRECISION //High precision, suitable for scenarios requiring high accuracy
   * @return 0 successful
   * @return -1 measurement_algorithm is invalid
   * @return -3 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
   * @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
   */
  int setMeasurementAlgorithm(uint8_t measurement_algorithm);

  /**
   * @fn getMeasurementAlgorithm
   * @brief Get the current measurement algorithm.
   * @return The current measurement algorithm.
   *         FAST_RESPONSE //Fast response,suitable for scenarios requiring quick response
   *         BALANCED //Balanced, suitable for scenarios where a balance needs to be struck between precision and rapid response
   *         HIGH_PRECISION //High precision, suitable for scenarios requiring high accuracy
   *         0 error, or not call openBmv080 or stopBmv080 function before.
   */
  uint8_t getMeasurementAlgorithm(void);

  bool devClass = true; // true for I2C, false for SPI

private:
  virtual uint8_t writeReg(uint16_t reg, const uint16_t* pBuf, size_t size) = 0;
  virtual uint8_t readReg(uint16_t reg, uint16_t* pBuf, size_t size) = 0;
  /**
   * @fn bmv080Write16BitCb
   * @brief Callback function for writing 16-bit data to the BMV080 sensor
   * @n This function is used to write 16-bit data to the BMV080 sensor via a serial communication interface.
   * @n This function is used internally by the BMV080 driver to write data to the sensor.
   * @param sercom_handle: Handle for the serial communication interface
   * @param reg: Register address to write to
   * @param data: Pointer to the data to write
   * @param size: Size of the data to write in bytes
   * @return Returns E_BMV080_OK if successful, otherwise returns a BMV080 status code.
   */
  static int8_t bmv080Write16BitCb(bmv080_sercom_handle_t, uint16_t, const uint16_t*, uint16_t);

  /**
   * @fn bmv080Read16BitCb
   * @brief Callback function for reading 16-bit data from the BMV080 sensor
   * @note This function is used to read 16-bit data from the BMV080 sensor via a serial communication interface.
   * @note This function is used internally by the BMV080 driver to read data from the sensor.
   * @param sercom_handle: Handle for the serial communication interface
   * @param reg: Register address to read from
   * @param data: Pointer to store the read data
   * @param size: Size of the data to read in bytes
   * @return Returns E_BMV080_OK if successful, otherwise returns a BMV080 status code.
   */
  static int8_t bmv080Read16BitCb(bmv080_sercom_handle_t, uint16_t, uint16_t*, uint16_t);

  /**
   * @fn getBmv080DataCb
   * @brief Callback function to handle BMV080 data
   * @note This function is called when new data is available from the BMV080 sensor.
   * @param bmv080_output: The output data from the BMV080 sensor
   * @param cb_parameters: Pointer to user-defined parameters for the callback
   */
  static void getBmv080DataCb(bmv080_output_t bmv080_output, void *cb_parameters);

  /**
   * @fn bmv080DelayCb
   * @brief Callback function to handle delay
   * @note This function is called when the BMV080 sensor needs to delay.
   * @param delay_ms: The delay in milliseconds
   * @return int8_t: The status of the delay
   */
  static int8_t bmv080DelayCb(uint32_t);

  /**
   * @fn bmv080DelayCyclingCb
   * @brief Callback function to handle delay for duty cycling
   * @note This function is called when the BMV080 sensor needs to delay for duty cycling.
   * @return uint32_t: The current time in milliseconds
   */
  static uint32_t bmv080DelayCyclingCb(void);

  /**
   * @fn get_bmv080Data
   * @brief Assign the data of BMV080 to the variable bmv080_output_t.
   * @param bmv080_output: Output structure containing the BMV080 sensor data
   * @note This fuction is called in the callback function getBmv080DataCb.
   * @return bool: Returns true if successful, otherwise returns false.
   */
  bool get_bmv080Data(bmv080_output_t bmv080_output);

  bmv080_handle_t _bmv080_handle_class = NULL;  // Handle for the BMV080 sensor.
  bmv080_output_t _bmv080Data; // BMV080 sensor data.
  bool _bmv080DataOK = false; // Flag to indicate if BMV080 data is ready.
};

class DFRobot_BMV080_I2C:public DFRobot_BMV080{
public:
  /**
   * @fn DFRobot_BMV080_I2C
   * @brief Constructor of DFRobot_BMV080_I2C class
   * @param Wire: Pointer to the TwoWire object for I2C communication
   * @note This constructor initializes the I2C communication interface for the BMV080 sensor
   */
  DFRobot_BMV080_I2C(TwoWire *Wire, uint8_t deviceAddr = DFRobot_BMV080_I2C_ADDR);

  /**
   * @fn begin
   * @brief Check if the sensor is connected.
   * @return 0 if the sensor is connected.
   * @return 1 if the sensor is not connected.
   */
  int begin(void);
  
  /**
   * @fn writeReg
   * @brief Write data to a register.
   * @param reg: The register address to write to.
   * @param pBuf: Pointer to the data buffer to write.
   * @param size: Size of the data buffer in bytes.
   * @return 0 if successful.
   * @return 1 if an error occurred.
   */
  uint8_t writeReg(uint16_t reg, const uint16_t* pBuf, size_t size);

  /**
   * @fn readReg
   * @brief Read data from a register.
   * @param reg: The register address to read from.
   * @param pBuf: Pointer to the buffer to store the read data.
   * @param size: Size of the buffer in bytes.
   * @return The number of bytes read if successful.
   * @return 0 if an error occurred.
   */
  uint8_t readReg(uint16_t reg, uint16_t* pBuf, size_t size);

  TwoWire *_pWire; // Pointer to the TwoWire object for I2C communication.
  uint8_t _deviceAddr; // I2C device address.
};

class DFRobot_BMV080_SPI:public DFRobot_BMV080{
public: 
  /**
   * @fn DFRobot_BMV080_SPI
   * @brief Constructor of DFRobot_BMV080_SPI class
   * @param spi: Pointer to the SPIClass object for SPI communication
   * @param csPin: Chip select pin for SPI communication
   * @note This constructor initializes the SPI communication interface for the BMV080 sensor
   */
  DFRobot_BMV080_SPI(SPIClass *spi, uint8_t csPin);

  /**
   * @fn begin
   * @brief Check if the sensor is connected.
   * @return 0 if the sensor is connected.
   * @return 1 if the sensor is not connected.
   */
  int begin(void);

  /**
   * @fn writeReg
   * @brief Write data to a register.
   * @param reg: The register address to write to.
   * @param pBuf: Pointer to the data buffer to write.
   * @param size: Size of the data buffer in bytes.
   * @return 0 if successful.
   * @return 1 if an error occurred.
   */
  uint8_t writeReg(uint16_t reg, const uint16_t* pBuf, size_t size);

  /**
   * @fn readReg
   * @brief Read data from a register.
   * @param reg: The register address to read from.
   * @param pBuf: Pointer to the buffer to store the read data.
   * @param size: Size of the buffer in bytes.
   * @return The number of bytes read if successful.
   * @return 0 if an error occurred.
   */
  uint8_t readReg(uint16_t reg, uint16_t* pBuf, size_t size);

private:
  SPIClass *_pSpi; // Pointer to the SPIClass object for SPI communication.
  uint8_t _csPin;  // spi cs pin
};

#endif
